﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using Microsoft.SharePoint;

namespace SPSource
{
    public class ModuleManager
    {
        private SPSourceFileInfo _spSourceFileInfo;

        public ModuleManager(SPSourceFileInfo spSourceFileInfo)
        {
            _spSourceFileInfo = spSourceFileInfo;
        }

        public void GetFiles(XmlNode module)
        {
            try
            {
                //get the list of files from the SPSource module element
                var filesElement = module.SelectNodes("//wss:File", _spSourceFileInfo.NamespaceManager);

                //get the relative url of the list to pull the files from
                var listAttrib = module.Attributes["Url"];
                //if the list url is missing, throw an error because if it isn't there, I mean, what's the point?
                if (listAttrib == null)
                    Utility.WriteError(string.Format("Missing url attribute in module"));
                var listUrl = listAttrib.Value;

                //make sure the list path does not start with a slash
                if (listUrl.StartsWith("/"))
                    listUrl = listUrl.Remove(0, 1);

                //make sure the list ends with a slash
                if (!listUrl.EndsWith("/"))
                    listUrl = listUrl + "/";

                //create the directory path to where the file lives
                var fileRootUrl = _spSourceFileInfo.DesignSiteUrl + listUrl;

                //create the XmlDocument for the provisioning module
                var moduleDocument = new XmlDocument();

                //create the root Elements element
                var rootNode = moduleDocument.CreateElement("Elements");
                moduleDocument.AppendChild(rootNode);

                //create the module element
                var moduleElement = moduleDocument.CreateElement("Module");

                //create the generic attribute container for creating the module's attributes
                XmlAttribute moduleAttribute;

                //get the module node from the SPSource file to allow for more control over the module attributes
                //and append them to the module element of the module document
                foreach (XmlAttribute moduleAttrib in module.Attributes)
                {
                    moduleAttribute = moduleDocument.CreateAttribute(moduleAttrib.Name);
                    moduleAttribute.Value = moduleAttrib.Value;
                    moduleElement.Attributes.Append(moduleAttribute);
                }

                var relativePath = Utility.GetRelativePathToSPSourceFileFromFeatureRoot(_spSourceFileInfo.SPSourceFileDirectoryInfo, _spSourceFileInfo.FeatureDirectory);

                //create the Name attrib from the directory name of the SPSource file
                if (moduleElement.Attributes["Name"] == null)
                {
                    moduleAttribute = moduleDocument.CreateAttribute("Name");
                    moduleAttribute.Value = _spSourceFileInfo.SPSourceFileDirectoryInfo.Name;
                    moduleElement.Attributes.Append(moduleAttribute);
                }

                //create the Url attribute to be the path to the list where the files are pulled from
                if (moduleElement.Attributes["Url"] == null)
                {
                    moduleAttribute = moduleDocument.CreateAttribute("Url");
                    moduleAttribute.Value = listAttrib.Value;
                    moduleElement.Attributes.Append(moduleAttribute);
                }

                //put the relative path to the SpSource file as the Path attribute 
                //we want to override this one if the user sets it to ensure it's correct
                if (moduleElement.Attributes["Path"] == null)
                {
                    moduleAttribute = moduleDocument.CreateAttribute("Path");
                    moduleAttribute.Value = relativePath;
                    moduleElement.Attributes.Append(moduleAttribute);
                }
                else
                {
                    moduleElement.Attributes["Path"].Value = relativePath;
                }

                var featureEntries = string.Empty;

                //I know Dan Larson will hate this, but since we don't have server context, we need to open the site as the service
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    //get the first file from the file list
                    //this is used to get a complete URL to the site
                    var file = filesElement[0];

                    //create a full path to the file so we ca nget the site
                    var filePath = fileRootUrl + file.Attributes["Url"].Value;
                    Utility.WriteMessage("Reverse engineering module file: "+ filePath);

                    //since we have the full path to a document in the site, we use that url to create the site object
                    using (SPSite site = new SPSite(filePath))
                    {
                        //since the site was opened with the full doc url, we can use the OpenWeb method to open the web which contains the files
                        using (SPWeb web = site.OpenWeb())
                        {
                            foreach (XmlElement fileElement in filesElement)
                            {
                                //create the full url to the file
                                var fileUrl = fileElement.Attributes["Url"].Value;
                                filePath = fileRootUrl + fileUrl;

                                //get the SPFile object for the file
                                var spFile = web.GetFile(filePath);

                                if (!spFile.Exists)
                                    Utility.WriteMessage(filePath + " could not be found");
                                else
                                {
                                    //get the file bytes from SP
                                    var fileBytes = spFile.OpenBinary(SPOpenBinaryOptions.SkipVirusScan);

                                    //check to make sure the file doesn't exist in the project
                                    //if it does, delete it s owe can re-write it
                                    var newFilePath = _spSourceFileInfo.SPSourceFileDirectoryInfo.FullName + "\\" + spFile.Name;
                                    if (File.Exists(newFilePath))
                                        File.Delete(newFilePath);

                                    //create a node in the module document for the file
                                    var fileNode = moduleDocument.CreateElement("File");

                                    //create a generic attribute node for the file
                                    //set the url in te module to the name of the file
                                    var fileAttrib = moduleDocument.CreateAttribute("Url");
                                    fileAttrib.Value = spFile.Name;
                                    fileNode.Attributes.Append(fileAttrib);

                                    var relativePathToFile = string.Empty;
                                    if (relativePath.Trim().Length > 0)
                                        relativePathToFile = relativePath + "\\" + spFile.Name;
                                    else
                                        relativePathToFile = spFile.Name;

                                    //get any user defined attributes in the file node and add them to the file element in the provisioner
                                    foreach (XmlAttribute sourceFileAttrib in fileElement.Attributes)
                                    {
                                        if (sourceFileAttrib.Name.ToUpper() == "URL")
                                            continue;

                                        fileAttrib = moduleDocument.CreateAttribute(sourceFileAttrib.Name);
                                        fileAttrib.Value = sourceFileAttrib.Value;
                                        fileNode.Attributes.Append(fileAttrib);
                                    }

                                    //set attributes in the provisioner file element which we know we need
                                    if (fileNode.Attributes["Type"] == null)
                                    {
                                        fileAttrib = moduleDocument.CreateAttribute("Type");
                                        fileAttrib.Value = "GhostableInLibrary";
                                        fileNode.Attributes.Append(fileAttrib);
                                    }

                                    if (fileNode.Attributes["IgnoreIfAlreadyExists"] == null)
                                    {
                                        fileAttrib = moduleDocument.CreateAttribute("IgnoreIfAlreadyExists");
                                        fileAttrib.Value = "TRUE";
                                        fileNode.Attributes.Append(fileAttrib);
                                    }

                                    //setup the file properties we want to ignore so they don't get set via the provisioner
                                    var excludedProperties = new List<string>();
                                    excludedProperties.Add("METAINFO");
                                    excludedProperties.Add("ORDER");
                                    excludedProperties.Add("FILELEAFREF");

                                    //get the file's parent list item so we cen get the properties to set in the provisioner
                                    var parentLI = spFile.Item;
                                    foreach (SPField field in parentLI.Fields)
                                    {
                                        //if it's a read only field, ignore it
                                        if (!field.ReadOnlyField)
                                        {
                                            var propName = field.StaticName;
                                            var propValue = string.Empty;

                                            //if it's excluded, ignore it
                                            if (excludedProperties.Contains(propName.ToUpper()))
                                                continue;

                                            //if the property value is null, ignore it
                                            if (parentLI[propName] != null)
                                                propValue = parentLI[propName].ToString();
                                            else
                                                continue;

                                            //reset the site url to the ~SiteCollection reserved key so it doesn't matter where the WSP is installed
                                            if (propValue.ToLower().Contains(_spSourceFileInfo.DesignSiteUrl))
                                            {
                                                propValue = propValue.ToLower().Replace(_spSourceFileInfo.DesignSiteUrl, "~SiteCollection/");
                                                propValue = propValue.ToLower().Replace(site.ServerRelativeUrl.ToLower(), "~SiteCollection");
                                            }

                                            //set the property in the provisioner file element
                                            var property = "<Property Name=\"" + propName + "\" Value=\"" + propValue + "\"/>";
                                            fileNode.InnerXml += property;
                                        }
                                    }

                                    //add the file element to the provisioner
                                    moduleElement.AppendChild(fileNode);

                                    //save the SPFile to the visual studio project
                                    var spFilePath = _spSourceFileInfo.SPSourceFileDirectoryInfo + "\\" + spFile.Name;
                                    FileStream fsFile = File.Create(spFilePath);
                                    fsFile.Write(fileBytes, 0, fileBytes.Length);
                                    fsFile.Close();

                                    FeatureManager.AddElementFileToFeature(new FileInfo(spFilePath));
                                }
                            }
                        }
                    }
                });

                //append the module node to the element node
                rootNode.AppendChild(moduleElement);

                //add the sharepoint namespace to the element node
                var nameSpace = moduleDocument.CreateAttribute("xmlns");
                nameSpace.Value = "http://schemas.microsoft.com/sharepoint/";
                rootNode.Attributes.Append(nameSpace);

                //check to make sure the user even wants a provisoining file
                var attribCreateProv = module.Attributes["createProvisioningFile"];

                //create the provisoining file alongside the feature.xml file.  if it's already there, delete it first so we can resave it
                var filePrefix = _spSourceFileInfo.SPSourceFileName.Replace(Constants.SPSourceExtension, "");
                var provisionerFileName = filePrefix + ".Provisioner.xml";
                var provFilePath = _spSourceFileInfo.SPSourceFileDirectoryInfo.FullName + "\\" + provisionerFileName;
                if (File.Exists(provFilePath))
                    File.Delete(provFilePath);

                //save the provisioning file
                moduleDocument.Save(provFilePath);

                if (!_spSourceFileInfo.OverrideFeatureDirectoryCheck)
                    FeatureManager.AddElementManifestToFeature(new FileInfo(provFilePath));
            }
            catch (Exception ex)
            {
                Utility.WriteError(ex.ToString());
                throw;
            }
        }
    }
}
